﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Asol.Reporting.Support;

namespace Asol.Reporting.Green
{
    /// <summary>
    /// Data specifická pro aplikaci (Green)
    /// </summary>
    public class App
    {
        #region Settings
        /// <summary>
        /// Konfigurace (singleton)
        /// </summary>
        public static Settings Settings { get {return Live._Settings; } }
        private Settings _Settings
        {
            get
            {
                if (this.__Settings == null)
                {
                    lock (this._LoadLock)
                    {
                        if (this.__Settings == null)
                        {
                            string file = Assistant.GetFileNameOnPath(Assistant.PathType.CommonApplicationData, "Reporting.config");
                            this.__Settings = Asol.Reporting.Green.Settings.Create(file);
                        }
                    }
                }
                return this.__Settings;
            }
        } private Settings __Settings;
        #endregion
        #region DbConnector
        /// <summary>
        /// Metoda otevře spojení do databáze dané connect stringem, 
        /// načte data daným SQL příkazem, 
        /// spojení zase zavře a data v tabulce vrátí.
        /// </summary>
        /// <param name="csb"></param>
        /// <param name="sql"></param>
        /// <returns></returns>
        public static Support.Data.Table LoadTable(System.Data.SqlClient.SqlConnectionStringBuilder csb, string sql)
        {
            Support.Data.Table table = null;
            using (System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection(csb.ConnectionString))
            {
                conn.Open();
                using (System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(sql, conn))
                using (System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.Default))
                {
                    cmd.CommandTimeout = 60;
                    table = Asol.Reporting.Support.Data.Table.CreateFromReader(reader);
                    reader.Close();
                }
                conn.Close();
            }
            return table;
        }
        #endregion
        #region DbConnectors
        /// <summary>
        /// Inicializace
        /// </summary>
        [InitializeMethod(-30000)]
        private void InitDbConnectors()
        {
            this._DbConnectionPool = new List<DbConnection>();
        }
        /// <summary>
        /// Vrátí použitelný DB konektor pro standardní databázi (Settings.ActiveProfile)
        /// </summary>
        /// <returns></returns>
        public static DbConnector GetDbConnector()
        {
            return Live._GetDbConnector(null);
        }
        /// <summary>
        /// Vrátí použitelný DB konektor pro explicitně specifikovanou databázi (parametr connectionString)
        /// </summary>
        /// <returns></returns>
        public static DbConnector GetDbConnector(string connectionString)
        {
            return Live._GetDbConnector(connectionString);
        }
        /// <summary>
        /// Vrátí použitelný DB konektor.
        /// Je třeba jej používat v using patternu, protože dispose tohoto objektu uvolní danou DB connection pro další požadavek.
        /// </summary>
        /// <returns></returns>
        private DbConnector _GetDbConnector(string connectionString)
        {
            bool isExplicit = !String.IsNullOrEmpty(connectionString);
            string connectString = (isExplicit ? connectionString : _Settings.ActiveProfile.ActiveConnectStringBuilder.ConnectionString);
            DbConnector dbConnector = null;
            lock (this._DbConnectionPool)
            {
                DbConnection dbConnection = null;
                if (!isExplicit)
                    this._RemoveInvalidConnection(connectString);
                dbConnection = this._DbConnectionPool.FirstOrDefault(dbc => dbc.IsReadyFor(connectString));
                if (dbConnection == null)
                {
                    dbConnection = new DbConnection(connectString);
                    this._DbConnectionPool.Add(dbConnection);
                }
                dbConnector = DbConnector.Create(dbConnection);
            }
            return dbConnector;
        }
        /// <summary>
        /// Odebere neplatné connections
        /// </summary>
        /// <param name="connectString"></param>
        private void _RemoveInvalidConnection(string connectString)
        {
            for (int i = this._DbConnectionPool.Count - 1; i >= 0; i--)
            {
                DbConnection dbc = this._DbConnectionPool[i];
                if (dbc.IsInvalidFor(connectString))
                {
                    ((IDisposable)dbc).Dispose();
                    this._DbConnectionPool.RemoveAt(i);
                }
            }
        }
        /// <summary>
        /// Soupis všech connections, trvalé objekty
        /// </summary>
        private List<DbConnection> _DbConnectionPool;
        #endregion
        #region DbMetadata
        /// <summary>
        /// Vrací true, pokud daná tabulka existuje.
        /// Pokud daný objekt není tabulkou, ale View, pak tato metoda jeho existenci nedetekuje, vrací false i kdyby View existovalo.
        /// </summary>
        /// <param name="tableName"></param>
        /// <returns></returns>
        public static bool IsDbTableExists(string tableName)
        {
            string sql = "SELECT (case when ((select object_id from sys.tables where object_id = object_id({{ObjectName}} )) is null) then 0 else 1 end)";
            sql = sql.Replace("{{ObjectName}} ", (new Support.Data.ValueData(tableName)).AsSql);          // Název tabulky (string) opatří apostrofy na začátku a na konci, a zdvojí apostrofy vnitřní.
            return (DbConnect.LoadValue(sql).ValueBoolean);         // SELECT vrací 0=neexistuje / 1=existuje, a třída Support.Data.ValueData hodnotu uloží jako Integer, a převede na Boolean (0=false / 1 = true)
        }
        /// <summary>
        /// Vrací true, pokud dané View existuje.
        /// Pokud daný objekt není View, ale tabulkou, pak tato metoda jeho existenci nedetekuje, vrací false i kdyby tabulka existovala.
        /// </summary>
        /// <param name="viewName"></param>
        /// <returns></returns>
        public static bool IsDbViewExists(string viewName)
        {
            string sql = "SELECT (case when ((select object_id from sys.views where object_id = object_id({{ObjectName}} )) is null) then 0 else 1 end)";
            sql = sql.Replace("{{ObjectName}} ", (new Support.Data.ValueData(viewName)).AsSql);          // Název view (string) opatří apostrofy na začátku a na konci, a zdvojí apostrofy vnitřní.
            return (DbConnect.LoadValue(sql).ValueBoolean);         // SELECT vrací 0=neexistuje / 1=existuje, a třída Support.Data.ValueData hodnotu uloží jako Integer, a převede na Boolean (0=false / 1 = true)
        }
        /// <summary>
        /// Načte a vrátí infromace o tabulce (nikoliv View) z databáze. Pokud tabulka neexistuje, vrací se informace bez řádků.
        /// Vrácená informace je uložena v Data.Table, kdes řádky popisují sloupce databázové tabulky, a sloupce vrácené Table obsahují jednotlivé detailní informace o konkrétním databázovém sloupci.
        /// Jde o sloupce: object_id, table_name, column_id, column_name, type_name, collation_name, is_ansi_padded, max_length, precision, is_identity, is_nullable, default_id, default_definition
        /// </summary>
        /// <param name="tableName"></param>
        /// <returns></returns>
        public static Asol.Reporting.Support.Data.Table GetDbTableInfo(string tableName)
        {
            string sql = @"SELECT tbl.object_id, tbl.name as table_name, col.column_id, col.name as column_name, typ.name as type_name, col.collation_name, 
 col.is_ansi_padded, col.max_length, col.precision, col.is_identity, col.is_nullable, col.default_object_id as default_id, def.definition as default_definition
FROM sys.tables tbl
 join sys.columns col on col.object_id = tbl.object_id
 join sys.types typ on typ.user_type_id = col.user_type_id
 left outer join sys.default_constraints def on def.parent_object_id = tbl.object_id and def.parent_column_id = col.column_id
WHERE tbl.object_id = object_id({{ObjectName}} )
ORDER BY col.column_id";
            sql = sql.Replace("{{ObjectName}} ", (new Support.Data.ValueData(tableName)).AsSql);          // Název tabulky (string) opatří apostrofy na začátku a na konci, a zdvojí apostrofy vnitřní.
            return DbConnect.LoadTable(sql);
        }
        /// <summary>
        /// Načte a vrátí infromace o View (nikoliv tabulce) z databáze. Pokud View neexistuje, vrací se informace bez řádků.
        /// Vrácená informace je uložena v Data.Table, kdes řádky popisují sloupce databázové tabulky, a sloupce vrácené Table obsahují jednotlivé detailní informace o konkrétním databázovém sloupci.
        /// Jde o sloupce: object_id, table_name, column_id, column_name, type_name, collation_name, is_ansi_padded, max_length, precision, is_identity, is_nullable, default_id, default_definition
        /// </summary>
        /// <param name="viewName"></param>
        /// <returns></returns>
        public static Asol.Reporting.Support.Data.Table GetDbViewInfo(string viewName)
        {
            string sql = @"SELECT tbl.object_id, tbl.name as table_name, col.column_id, col.name as column_name, typ.name as type_name, col.collation_name, 
 col.is_ansi_padded, col.max_length, col.precision, col.is_identity, col.is_nullable, col.default_object_id as default_id, def.definition as default_definition
FROM sys.views tbl
 join sys.columns col on col.object_id = tbl.object_id
 join sys.types typ on typ.user_type_id = col.user_type_id
 left outer join sys.default_constraints def on def.parent_object_id = tbl.object_id and def.parent_column_id = col.column_id
WHERE tbl.object_id = object_id({{ObjectName}} )
ORDER BY col.column_id";
            sql = sql.Replace("{{ObjectName}} ", (new Support.Data.ValueData(viewName)).AsSql);          // Název tabulky (string) opatří apostrofy na začátku a na konci, a zdvojí apostrofy vnitřní.
            return DbConnect.LoadTable(sql);
        }
        #endregion
        #region Singleton
        /// <summary>
        /// Přístup k instanci singletonu
        /// </summary>
        protected static App Live
        {
            get
            {
                if (_Live == null)
                {
                    lock (_Lock)
                    {
                        if (_Live == null)
                            _Live = new App();
                    }
                }
                return _Live;
            }
        } private static App _Live;
        /// <summary>
        /// Privátní konstruktor zajistí provedení inicializaci
        /// </summary>
        private App()
        {
            InitializeMethodAttribute.CallInitializeMethods(this);
        }
        private static object _Lock = new object();
        private object _LoadLock = new object();
        #endregion
        #region Report Edit, Print, ...
        /// <summary>
        /// Zajistí editaci daného reportu
        /// </summary>
        /// <param name="templateNumber"></param>
        internal static void ReportEdit(int templateNumber)
        {
            TemplateInfo template = TemplateInfo.LoadTemplate(templateNumber);
            template.ReportEdit();
        }
        /// <summary>
        /// Zajistí editaci daného reportu
        /// </summary>
        /// <param name="template"></param>
        internal static void ReportEdit(TemplateInfo template)
        {
            template.ReportEdit();
            // Asol.Reporting.Editor.Data.ReportDocument document = Editor.Data.ReportDocument.CreateFromContent(template.FrxContent);
            // document.Edit();
            // Asol.Reporting.Editor.Forms.EditorForm ef = new Editor.Forms.EditorForm();
            //ef.Document = document;
            //ef.ShowDialog();
            // PrepareDbStructure();

            // Assistant.PblLibraryRead
            // App.Settings.ActiveServer.
        }
        #endregion

        private static void PrepareDbStructure()
        {
            DbConnect.Execute(@"if not exists (select * from sys.columns where name = 'frx_report' and object_id = object_id('lcs.noris_templates'))
	alter table lcs.noris_templates add frx_report nvarchar(max)");
            DbConnect.Execute(@"if not exists (select * from sys.columns where name = 'frx_message' and object_id = object_id('lcs.noris_templates'))
	alter table lcs.noris_templates add frx_message nvarchar(max)");

            /*
            SELECT col.name, typ.name, col.max_length, col.*
            FROM sys.tables tbl
            join sys.columns col on col.object_id = tbl.object_id
            join sys.types typ on typ.user_type_id = col.user_type_id
            WHERE tbl.object_id = object_id('lcs.noris_templates')
            ORDER BY tbl.name, col.column_id
             */
        }
    }
    #region classes DbConnection, DbConnector, DbConnect
    /// <summary>
    /// Trvalá obálka pro jednu DB connection.
    /// Její životnost je po celou dobu běhu systému (do změny konfigurace), obaluje fyzickou connection.
    /// Pro jednotlivé požadavky pak zapůjčuje DbConnector, jehož životnost je jedna zápůjčka (několik operací za sebou).
    /// </summary>
    internal class DbConnection : IDisposable
    {
        #region Konstrukce
        
        internal DbConnection(string connectString)
        {
            this._ConnectString = connectString;
            this._Connection = new System.Data.SqlClient.SqlConnection(connectString);
        }
        private string _ConnectString;
        private System.Data.SqlClient.SqlConnection _Connection;
        /// <summary>
        /// Connect string této connection
        /// </summary>
        internal string ConnectString { get { return this._ConnectString; } }
        /// <summary>
        /// SqlConnection
        /// </summary>
        public System.Data.SqlClient.SqlConnection Connection { get { return this._Connection; } }
        #endregion
        #region Connect, Close, Dispose
        /// <summary>
        /// Zajistí, že Connection bude Open
        /// </summary>
        private void _CheckStatus()
        {
            if (this._Connection.State != System.Data.ConnectionState.Open)
                this._ReConnect();
        }
        /// <summary>
        /// Odpojí a zapojí
        /// </summary>
        private void _ReConnect()
        {
            this._Close();
            this._Connect();
        }
        /// <summary>
        /// Odpojí
        /// </summary>
        private void _Close()
        {
            try
            {
                if (this._Connection != null)
                {
                    if (this._Connection.State == System.Data.ConnectionState.Open)
                        this._Connection.Close();
                    this._Connection.Dispose();
                }
            }
            catch (Exception)
            { }
            finally
            {
                this._Connection = null;
            }
        }
        /// <summary>
        /// Zapojí
        /// </summary>
        private void _Connect()
        {
            this._Connection = new System.Data.SqlClient.SqlConnection(this._ConnectString);
            this._Connection.Open();
        }
        /// <summary>
        /// Zahodí
        /// </summary>
        void IDisposable.Dispose()
        {
            this._Close();
        }
        #endregion

        internal bool IsInvalidFor(string connectString)
        {
            return (this._ConnectString != connectString && !this.IsInUse);
        }

        internal bool IsReadyFor(string connectString)
        {
            return (this._ConnectString == connectString && !this.IsInUse);
        }
        #region Zápůjčka: begin a end

        internal void LoanBegin()
        {
            this.IsInUse = true;
            this._CheckStatus();
        }

        internal void LoanEnd()
        {
            this.IsInUse = false;
        }
        private bool IsInUse { get; set; }
        #endregion
    }
    /// <summary>
    /// Pracovní obálka jedné DB connection.
    /// Její životnost je jedna zápůjčka, po kterou je connection v používání v aplikaci (v using patternu).
    /// Po skončení používání je interní connection vrácena do výchozího stavu a připravena k další zápůjčce.
    /// </summary>
    public class DbConnector : IDisposable
    {
        #region Konstruktor a Dispose
        internal static DbConnector Create(DbConnection dbConnection)
        {
            return new DbConnector(dbConnection);
        }
        private DbConnector(DbConnection dbConnection)
        {
            this._DbConnection = dbConnection;
            this._DbConnection.LoanBegin();
        }
        private DbConnection _DbConnection;
        void IDisposable.Dispose()
        {
            this._DbConnection.LoanEnd();
        }
        #endregion
        #region Public instanční metody
        /// <summary>
        /// Načte a vrátí tabulku daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public Support.Data.Table LoadTable(string sql)
        {
            return this._LoadTable(sql, null);
        }
        /// <summary>
        /// Načte a vrátí tabulku daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public Support.Data.Table LoadTable(string sql, Int32? timeOut)
        {
            return this._LoadTable(sql, timeOut);
        }
        /// <summary>
        /// Načte a vrátí hodnotu daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public Support.Data.ValueData LoadValue(string sql)
        {
            return this._LoadValue(sql, null);
        }
        /// <summary>
        /// Načte a vrátí hodnotu daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public Support.Data.ValueData LoadValue(string sql, Int32? timeOut)
        {
            return this._LoadValue(sql, timeOut);
        }
        /// <summary>
        /// Provede daný SQL příkaz
        /// </summary>
        /// <param name="sql"></param>
        public void Execute(string sql)
        {
            this._Execute(sql, null);
        }
        /// <summary>
        /// Provede daný SQL příkaz
        /// </summary>
        /// <param name="sql"></param>
        public void Execute(string sql, Int32? timeOut)
        {
            this._Execute(sql, timeOut);
        }
        #endregion
        #region Privátní výkonné metody
        /// <summary>
        /// Načte a vrátí tabulku daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        private Support.Data.Table _LoadTable(string sql, Int32? timeOut)
        {
            Support.Data.Table table = null;
            using (System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(sql, this._DbConnection.Connection))
            using (System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.Default))
            {
                if (timeOut.HasValue) cmd.CommandTimeout = timeOut.Value;
                table = Asol.Reporting.Support.Data.Table.CreateFromReader(reader);
                reader.Close();
            }
            return table;
        }
        /// <summary>
        /// Načte a vrátí hodnotu daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        private Support.Data.ValueData _LoadValue(string sql, Int32? timeOut)
        {
            Support.Data.Table table = null;
            using (System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(sql, this._DbConnection.Connection))
            using (System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.Default))
            {
                if (timeOut.HasValue) cmd.CommandTimeout = timeOut.Value;
                table = Asol.Reporting.Support.Data.Table.CreateFromReader(reader);
                reader.Close();
            }
            if (table.ColumnCount > 0 && table.RowCount > 0) return table.Rows[0][0];
            return new Support.Data.ValueData();
        }
        /// <summary>
        /// Provede daný SQL příkaz
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        private void _Execute(string sql, Int32? timeOut)
        {
            using (System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand(sql, this._DbConnection.Connection))
            {
                if (timeOut.HasValue) cmd.CommandTimeout = timeOut.Value;
                cmd.ExecuteNonQuery();
            }
        }
        #endregion
    }
    /// <summary>
    /// Statické metody pro jednorázový přístup k databázi
    /// </summary>
    public static class DbConnect
    {
        #region Statické metody
        /// <summary>
        /// Načte a vrátí tabulku daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public static Support.Data.Table LoadTable(string sql)
        {
            using (DbConnector connector = App.GetDbConnector())
            {
                return connector.LoadTable(sql);
            }
        }
        /// <summary>
        /// Načte a vrátí tabulku daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public static Support.Data.Table LoadTable(string sql, Int32? timeOut)
        {
            using (DbConnector connector = App.GetDbConnector())
            {
                return connector.LoadTable(sql, timeOut);
            }
        }
        /// <summary>
        /// Načte a vrátí tabulku daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public static Support.Data.Table LoadTable(string sql, string connectionString)
        {
            using (DbConnector connector = App.GetDbConnector(connectionString))
            {
                return connector.LoadTable(sql);
            }
        }
        /// <summary>
        /// Načte a vrátí tabulku daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public static Support.Data.Table LoadTable(string sql, Int32? timeOut, string connectionString)
        {
            using (DbConnector connector = App.GetDbConnector(connectionString))
            {
                return connector.LoadTable(sql, timeOut);
            }
        }
        /// <summary>
        /// Načte a vrátí hodnotu daným SQL dotazem
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public static Support.Data.ValueData LoadValue(string sql)
        {
            using (DbConnector connector = App.GetDbConnector())
            {
                return connector.LoadValue(sql);
            }
        }
        /// <summary>
        /// Provede daný SQL příkaz
        /// </summary>
        /// <param name="sql"></param>
        public static void Execute(string sql)
        {
            using (DbConnector connector = App.GetDbConnector())
            {
                connector.Execute(sql);
            }
        }
        /// <summary>
        /// Provede daný SQL příkaz
        /// </summary>
        /// <param name="sql"></param>
        public static void Execute(string sql, Int32? timeOut)
        {
            using (DbConnector connector = App.GetDbConnector())
            {
                connector.Execute(sql, timeOut);
            }
        }
        #endregion
    }
    #endregion
}
